Skip to content

[distributed] Context parallel module, with Ulysses - #50493

Draft
3outeille wants to merge 9 commits into
mainfrom
cp-1-implementations
Draft

3outeille wants to merge 9 commits into
mainfrom
cp-1-implementations

Conversation

@3outeille

@3outeille 3outeille commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Stack: this PR → #50496 → #50511 → #50514 → #50499 → #50521 → #50522

Adds transformers/distributed/context_parallel.py, the whole context parallel (CP) module. Nothing is wired yet: no DistributedConfig field, no plan on the configs, no cp mesh axis. #50496 brings those.

What this PR brings

  • ContextParallelLayer, the base of the styles below, with the install_forward contract of the TP and EP styles: it replaces module.forward by one that runs transform_inputs_pre_forward first (with functools.wraps, so inspect.signature(model.forward) stays what Trainer expects).
  • "cp_split_sequence" (SplitSequenceParallel), for decoder layers: they run on this rank's contiguous slice of the sequence. The first layer of the plan cuts hidden_states, and every layer cuts the position_embeddings. The attention mask and position_ids stay those the model built for the whole sequence.
  • ContextParallelAttention, the base of the attention styles: install_forward checks the module, gives it attention as its cp_attention, and wraps the model's entry in ALL_ATTENTION_FUNCTIONS so that the attention call of that module runs through it. The algorithm is a plan style, like a TP style: "cp_ulysses" (UlyssesAttention) all-to-alls the query, key and value to the whole sequence with num_heads / cp_size heads, runs the model's own attention function with the model's own mask, and all-to-alls the output back. Sliding windows, chunked attention, padding and packing therefore need no CP mask code. Another algorithm is a subclass registered in ALL_PARALLEL_STYLES and named in the plan; context_parallel_styles() lists them for the plan validation.
  • ModelContextParallel, the style of the model itself, outside the plan since no plan key names the root module: its forward refuses a cache, and pads every sequence at its end to a multiple of cp_size before the model builds its masks (input_ids get pad_token_id or 0, inputs_embeds and attention_mask zeros, labels and shift_labels -100, position_ids continue the last document). The logits are this rank's slice of the padded sequence.
  • context_parallel_loss, set as model.loss_function: every rank reports the loss of the whole sequences, computed from its slice of the logits and its slice of the shifted labels, with a gradient of this rank's times cp_size, so that FSDP2's average over the cp ranks is the gradient of the full-sequence loss.
  • apply_context_parallelism(model, cp_mesh, cp_plan): refuses what CP would silently get wrong (attention that does not go through the attention interface, layer types other than softmax attention, heads other than a causal LM), then installs ModelContextParallel and the loss on the model and the plan's styles on their modules.
  • all_to_all and split_sequence, the two helpers the above use. The all-to-all is torch's all_to_all_single_autograd, which EP uses too.

What #50496 brings

DistributedConfig(cp_size, cp_plan), base_model_cp_plan on the configs and the mixin's plan resolution, the cp mesh axis with FSDP2 over fsdp × cp, the models that opt out, the Trainer refusal, and the end-to-end tests through from_pretrained(distributed_config=...). It adds no code to context_parallel.py.

Tests

tests/context_parallel/test_context_parallel.py, on 2 CPU ranks (gloo):

  • the all-to-all and its backward;
  • Ulysses against sdpa on the full sequence with the full mask (causal, packed, sliding window, GQA), forward and backward;
  • the keyword arguments passing through unchanged (sinks are cut to this rank's heads);
  • the split style, the routing of the attention call through an attention style, the Ulysses checks and the model checks, the registered styles;
  • the loss against the full-sequence loss: value, gradient, a caller's num_items_in_batch, a slice whose shifted labels are all ignored;
  • a tiny Llama with the plan installed by hand, against the same weights on the full sequences, with 8 and with 7 tokens (padded to 8): this rank's logits at the real positions, the loss and every gradient; the cache is refused, model.forward keeps its signature;
  • what the padding does to each input, single process.

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@3outeille
3outeille added this pull request to stack #50502 November 9, 2026 11:03
@3outeille
3outeille removed this pull request from stack #50502 November 9, 2026 14:29
@3outeille
3outeille added this pull request to stack #50518 November 9, 2026 14:30
@3outeille
3outeille removed this pull request from stack #50518 November 9, 2026 14:36
@3outeille
3outeille added this pull request to stack #50520 November 9, 2026 14:37
@3outeille
3outeille removed this pull request from stack #50520 November 10, 2026 02:50
@3outeille
3outeille force-pushed the cp-1-implementations branch from ba95ec6 to a36d4f7 Compare November 10, 2026 02:50
@3outeille
3outeille added this pull request to stack #50546 November 10, 2026 02:51
@3outeille
3outeille force-pushed the cp-1-implementations branch 2 times, most recently from d0e004f to 16affa7 Compare November 10, 2026 05:44
@3outeille
3outeille force-pushed the cp-1-implementations branch 3 times, most recently from 6b06ac3 to 4d31656 Compare November 12, 2026 08:01
@3outeille 3outeille changed the title [distributed] Context parallel implementations, with Ulysses [distributed] Context parallel module, with Ulysses Nov 12, 2026
@3outeille
3outeille force-pushed the cp-1-implementations branch from 4d31656 to 6e942d0 Compare November 12, 2026 09:13
Each rank of a `cp` device mesh holds a contiguous slice of every sequence. The plan pairs
two kinds of styles: `cp_split_sequence` cuts the layer inputs to that slice, and an attention
style, `cp_ulysses`, runs the attention call of the attention modules over the whole sequence,
with the mask and the keyword arguments the model built before the sequence was cut. Ulysses
is two all-to-all exchanges around the model's own attention function; another algorithm is a
`ContextParallelAttention` subclass registered in `ALL_PARALLEL_STYLES`. The styles share
`ContextParallelLayer`, with the `install_forward` contract of the TP and EP styles.

`apply_context_parallelism` installs a resolved plan on a model, and `ModelContextParallel`
on the model itself: the model checks, a forward without a cache, and the loss of the whole
sequences, which every rank reports.

Not wired into `DistributedConfig` yet: nothing changes for users.
The all-to-all and its backward, Ulysses against plain sdpa on the whole sequence given the
whole mask (unpacked, packed, sliding window, with and without GQA), the split style on a
stand-in layer, the routing of the attention call, the Ulysses and model checks, the loss
against the full-sequence loss, and a tiny Llama with the plan installed by hand against the
same weights on the full sequences. The multi-rank tests use two CPU ranks through
`run_distributed`, and carry `is_fsdp_test` so that the sharded CI jobs own them.
@3outeille
3outeille force-pushed the cp-1-implementations branch from 6e942d0 to 318ef65 Compare November 12, 2026 09:29
The context parallel styles live in their own registry, `ALL_CP_STYLES`, like `ALL_FSDP_STYLES`,
instead of `ALL_PARALLEL_STYLES` plus a filter on their class. `apply_context_parallelism` reads
it, and another attention algorithm is added to it under its own name. The layer types that
`ModelContextParallel.check` accepts are inlined in the check.
…ks in `apply_context_parallelism`, pad the sequences

- `context_parallel_loss` is a module-level function that `apply_context_parallelism` sets as
  `model.loss_function`, the seam transformers has for a model's loss; `ModelContextParallel` is
  about the forward only.
- The model checks open `apply_context_parallelism` instead of a `check` method.
- A sequence length that `cp_size` does not divide is padded at its end to a multiple of it, in
  `ModelContextParallel.transform_inputs_pre_forward`, before the model builds its masks:
  `input_ids` get `pad_token_id` (or 0), `inputs_embeds` zeros, `attention_mask` zeros, `labels`
  and `shift_labels` -100, and `position_ids` continue those of the last document. The logits are
  this rank's slice of the padded sequence; the loss counts the real tokens only. Was #50515.
@3outeille
3outeille removed this pull request from stack #50546 November 12, 2026 10:09
@3outeille
3outeille added this pull request to stack #50648 November 12, 2026 10:10
…he two transforms of a style

`ContextParallelLayer` gains `transform_output_post_forward`, so a style can rewrite the output of the
forward it wraps, as the tensor parallel styles do. `ModelContextParallel` uses both: its inputs lose
the cache, are padded to a multiple of `cp_size`, and carry this rank's slice of the shifted labels with
the token count of the whole sequences, which the model's own loss function takes as kwargs; its output
reports the loss of the whole sequences on every rank, with the gradient of this rank's slice scaled
by `cp_size` for FSDP2's average. `context_parallel_loss` and the replacement of `model.loss_function`
are gone.
`ALL_CP_STYLES` lists everything context parallelism installs, the model's own style included, and
`apply_context_parallelism` installs the root through the same loop as the plan, under the name
`named_modules` gives it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants